package com.dajia.hefei.nbiot.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

@Slf4j
public class HttpsUtil {
    private final static String CONTENT_LENGTH = "Content-Length";

    private static HttpClient httpClient;

    public static HttpClient getClient() {
        return httpClient;
    }

    public static void initSSLConfigForTwoWay(InputStream certInputStream, char[] certPwd, InputStream caInputStream, char[] caPwd) {
        try {
            KeyStore keyStore = KeyStore.getInstance("pkcs12");
            keyStore.load(certInputStream, certPwd);
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
            kmf.init(keyStore, certPwd);

            keyStore = KeyStore.getInstance("jks");
            keyStore.load(caInputStream, caPwd);
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
            tmf.init(keyStore);

            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

            SSLConnectionSocketFactory ssf = new SSLConnectionSocketFactory(sc, new NoopHostnameVerifier());

            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("https", ssf).build();
            PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            httpClient = HttpClients.custom().setConnectionManager(cm).build();
        } catch (Exception e) {
            log.error("initSSLConfigForTwoWay error: ", e);
        }
    }

    public static StreamClosedHttpResponse doPostJson(String url, Map<String, String> headerMap, String content) {
        HttpPost request = new HttpPost(url);
        addRequestHeader(request, headerMap);
        request.setEntity(new StringEntity(content, ContentType.APPLICATION_JSON));
        return (StreamClosedHttpResponse) executeHttpRequest(request);
    }

    public static StreamClosedHttpResponse doPostMultipartFile(String url, Map<String, String> headerMap, File file) {
        HttpPost request = new HttpPost(url);
        addRequestHeader(request, headerMap);
        FileBody fileBody = new FileBody(file);
        HttpEntity reqEntity = (HttpEntity) MultipartEntityBuilder.create().addPart("file", fileBody).build();
        request.setEntity(reqEntity);
        return (StreamClosedHttpResponse) executeHttpRequest(request);
    }

    public static StreamClosedHttpResponse doPostJsonGetStatusLine(String url, Map<String, String> headerMap, String content) {
        HttpPost request = new HttpPost(url);
        addRequestHeader(request, headerMap);
        request.setEntity(new StringEntity(content, ContentType.APPLICATION_JSON));
        HttpResponse response = executeHttpRequest(request);
        if (null == response) {
            System.out.println("The response body is null.");
            log.info("POST [{},{},{}], The response body is null.", url, headerMap, content);
        }
        return (StreamClosedHttpResponse) response;
    }

    public static StreamClosedHttpResponse doPostJsonGetStatusLine(String url, String content) {
        HttpPost request = new HttpPost(url);
        request.setEntity(new StringEntity(content, ContentType.APPLICATION_JSON));
        HttpResponse response = executeHttpRequest(request);
        if (null == response) {
            System.out.println("The response body is null.");
            log.info("POST [{},{}], The response body is null.", url, content);
        }
        return (StreamClosedHttpResponse) response;
    }

    private static List<NameValuePair> paramsConverter(Map<String, String> params) {
        List<NameValuePair> pairList = new LinkedList<>();
        for (Map.Entry<String, String> paramEntry : params.entrySet()) {
            pairList.add(new BasicNameValuePair(paramEntry.getKey(), paramEntry.getValue()));
        }
        return pairList;
    }

    public static StreamClosedHttpResponse doPostFormUrlEncodedGetStatusLine(String url, Map<String, String> formParams) {
        HttpResponse response = null;
        try {
            HttpPost request = new HttpPost(url);
            request.setEntity(new UrlEncodedFormEntity(paramsConverter(formParams)));
            response = executeHttpRequest(request);
            if (null == response) {
                System.out.println("The response body is null.");
                log.info("POST [{},{}], The response body is null.", url, formParams);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            log.error("POST [{},{}], UnsupportedEncodingException: {}", url, formParams, e.getMessage());
        }
        return (StreamClosedHttpResponse) response;
    }

    public static StreamClosedHttpResponse doPutJson(String url, Map<String, String> headerMap, String content) {
        HttpPut request = new HttpPut(url);
        addRequestHeader(request, headerMap);
        request.setEntity(new StringEntity(content, ContentType.APPLICATION_JSON));
        return (StreamClosedHttpResponse) executeHttpRequest(request);
    }

    public static HttpResponse doPut(String url, Map<String, String> headerMap) {
        HttpPut request = new HttpPut(url);
        addRequestHeader(request, headerMap);
        return executeHttpRequest(request);
    }

    public static StreamClosedHttpResponse doPutJsonGetStatusLine(String url, Map<String, String> headerMap, String content) {
        HttpResponse response = doPutJson(url, headerMap, content);
        if (null == response) {
            System.out.println("The response body is null.");
            log.info("PUT [{},{},{}], The response body is null.", url, headerMap, content);
        }
        return (StreamClosedHttpResponse) response;
    }

    public static StreamClosedHttpResponse doPutGetStatusLine(String url, Map<String, String> headerMap) {
        HttpResponse response = doPut(url, headerMap);
        if (null == response) {
            log.info("PUT [{},{}], The response body is null.", url, headerMap);
            System.out.println("The response body is null.");
        }
        return (StreamClosedHttpResponse) response;
    }

    public static HttpResponse doGetWithParas(String url, Map<String, String> queryParams, Map<String, String> headerMap) {
        try {
            HttpGet request = new HttpGet();
            addRequestHeader(request, headerMap);
            URIBuilder builder;
            builder = new URIBuilder(url);
            if (queryParams != null && !queryParams.isEmpty()) {
                builder.setParameters(paramsConverter(queryParams));
            }
            request.setURI(builder.build());
            return executeHttpRequest(request);
        } catch (URISyntaxException e) {
            System.out.printf("URISyntaxException: {}", e);
            log.error("URISyntaxException: ", e);
        }
        return null;
    }

    public static StreamClosedHttpResponse doGetWithParasGetStatusLine(String url, Map<String, String> queryParams, Map<String, String> headerMap) {
        HttpResponse response = doGetWithParas(url, queryParams, headerMap);
        if (null == response) {
            System.out.println("The response body is null.");
            log.info("GET [{},{},{}], The response body is null.", url, headerMap, queryParams);
        }
        return (StreamClosedHttpResponse) response;
    }

    public static HttpResponse doDelete(String url, Map<String, String> headerMap) {
        HttpDelete request = new HttpDelete(url);
        addRequestHeader(request, headerMap);
        return executeHttpRequest(request);
    }

    public static HttpResponse doDeleteWithParam(String url, Map<String, String> queryParams, Map<String, String> headerMap) {
        try {
            HttpDelete request = new HttpDelete();
            URIBuilder builder;
            builder = new URIBuilder(url);
            if (queryParams != null && !queryParams.isEmpty()) {
                builder.setParameters(paramsConverter(queryParams));
            }
            request.setURI(builder.build());
            addRequestHeader(request, headerMap);
            return executeHttpRequest(request);
        } catch (URISyntaxException e) {
            System.out.printf("URISyntaxException: {}", e);
            log.error("URISyntaxException: ", e);
        }
        return null;
    }

    public static StreamClosedHttpResponse doDeleteGetStatusLine(String url, Map<String, String> headerMap) {
        HttpResponse response = doDelete(url, headerMap);
        if (null == response) {
            System.out.println("The response body is null.");
            log.info("DELETE [{},{}], The response body is null.", url, headerMap);
        }
        return (StreamClosedHttpResponse) response;
    }

    public static StreamClosedHttpResponse doDeleteWithParasGetStatusLine(String url, Map<String, String> queryParams, Map<String, String> headerMap) {
        HttpResponse response = doDeleteWithParam(url, queryParams, headerMap);
        if (null == response) {
            System.out.println("The response body is null.");
            log.info("DELETE [{},{}], The response body is null.", url, headerMap);
        }
        return (StreamClosedHttpResponse) response;
    }

    private static void addRequestHeader(HttpUriRequest request, Map<String, String> headerMap) {
        if (null == headerMap || headerMap.isEmpty()) {
            return;
        }
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            if (CONTENT_LENGTH.equalsIgnoreCase(entry.getKey())) {
                continue;
            }
            request.addHeader(entry.getKey(), entry.getValue());
        }
    }

    private static HttpResponse executeHttpRequest(HttpUriRequest request) {
        HttpResponse response = null;
        try {
            response = httpClient.execute(request);
        } catch (Exception e) {
            System.out.println("executeHttpRequest failed.");
            log.error("executeHttpRequest failed.", e);
        } finally {
            try {
                response = new StreamClosedHttpResponse(response);
            } catch (IOException e) {
                System.out.println("IOException: " + e.getMessage());
                log.error("IOException: " + e.getMessage(), e);
            }
        }
        return response;
    }

    public static String getHttpResponseBody(HttpResponse response) {
        if (response == null) {
            return null;
        }
        String body = null;
        if (response instanceof StreamClosedHttpResponse) {
            body = ((StreamClosedHttpResponse) response).getContent();
        } else {
            HttpEntity entity = response.getEntity();
            if (entity != null && entity.isStreaming()) {
                String encoding = entity.getContentEncoding() != null ? entity.getContentEncoding().getValue() : null;
                try {
                    body = IOUtils.toString(entity.getContent(), encoding);
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("IOException: " + e.getMessage(), e);
                }
            }
        }
        return body;
    }
}
